package golf.stockinfo.buysellsimulation;

import golf.stockinfo.buysellsimulation.SimulationData.TransactionType;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream.GetField;
import java.sql.Date;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;

import org.joda.time.DateTime;
import org.joda.time.Days;

public class SimulateSET50AndTFEX {
	
	//The best SMAV is 9
	public static String weeklyFileName = "SET-Weekly-19900102-20121102-MOV9.txt";
	public static String dailyFileName = "SET-Daily-19900102-20121102.txt";
	
	public static float INITIAL_CAPITAL = 500000;
	public static float TFEX_COST_PER_POINT = 1000;
	public static float CUT_LOSS_PERCENTAGE = -0.08f;
	//The best cut loss is -13,000
	public static float CUT_LOSS_AMOUNT_TFEX = -13000.00f;
	public static Date START_DATE = Date.valueOf("1996-01-01");
	public static int SIMULATION_LENGTH_IN_YEAR = 10;
	
	private TreeMap<DateTime, MarketData> weeklyData;
	private TreeMap<DateTime, MarketData> dailyData;
	private ArrayList<SimulationData> simulationDataArray = new ArrayList<SimulationData>();
	private static DecimalFormat numberFormat = new DecimalFormat("###,###.00");
	private static DecimalFormat daysFormat = new DecimalFormat("###,###");

	/**
	 * 
	 * The best Moving average to be used is 9 weeks or SMAV(9)
	 * 
	 * @param args
	 */
	public static void main(String[] args) {

		SimulateSET50AndTFEX ths = new SimulateSET50AndTFEX();
		ths.execute(true);
		
		System.out.println(ths.simulationDataArray.get(0).getStartDate() + ";" + ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate() + 
				";" + numberFormat.format(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance()));
		
		DateTime dt1 = new DateTime(ths.simulationDataArray.get(0).getStartDate());
		DateTime dt2 = new DateTime(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate());
		
		System.out.println("======================================================");
		System.out.println("Summary");
		System.out.println("======================================================");
		System.out.println("Investment start date      : " + ths.simulationDataArray.get(0).getStartDate());
		System.out.println("Investment end date        : " + ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate());
		System.out.println("Investment time (days)     : " + daysFormat.format(Days.daysBetween(dt1, dt2).getDays()));
		System.out.println("Start Investment amount    : " + numberFormat.format(INITIAL_CAPITAL));
		System.out.println("Total amount of end balance: " + numberFormat.format(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance()));
		String roundOffPercent = Float.toString((getApproxEquivalentInterestPerYear(
				INITIAL_CAPITAL, 
				ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance(), 
				Days.daysBetween(dt1, dt2).getDays())) * 100).split("\\.")[0];
		System.out.println("Approximate Interest/year  : " + roundOffPercent + "%");
		System.out.println("======================================================");

		/*
		 * Down here is commented out, they are for finding the best combination of all factors
		 */
//		ArrayList<SimulationData> totalSimulationResult = new ArrayList<SimulationData>();
//		
//		Date[] dateArr = { Date.valueOf("1990-06-30"), Date.valueOf("1990-12-27"), Date.valueOf("1991-06-25"),
//				Date.valueOf("1991-12-22"), Date.valueOf("1992-06-19"), Date.valueOf("1992-12-16"), Date.valueOf("1993-06-14"),
//				Date.valueOf("1993-12-11"), Date.valueOf("1994-06-09"), Date.valueOf("1994-12-06"), Date.valueOf("1995-06-04"),
//				Date.valueOf("1995-12-01"), Date.valueOf("1996-05-29"), Date.valueOf("1996-11-25"), Date.valueOf("1997-05-24"),
//				Date.valueOf("1997-11-20"), Date.valueOf("1998-07-01"), Date.valueOf("1998-11-15"), Date.valueOf("1999-05-14"),
//				Date.valueOf("1999-11-10"), Date.valueOf("2000-05-08"), Date.valueOf("2000-11-04"), Date.valueOf("2001-05-03"),
//				Date.valueOf("2001-10-30"), Date.valueOf("2002-04-28"), Date.valueOf("2002-10-25"), Date.valueOf("2003-04-23"),
//				Date.valueOf("2003-10-20"), Date.valueOf("2004-04-17"), Date.valueOf("2004-10-14"), Date.valueOf("1990-08-01"),
//				Date.valueOf("1991-01-28"), Date.valueOf("1991-07-27"), Date.valueOf("1992-01-23"), Date.valueOf("1992-07-21"),
//				Date.valueOf("1993-01-17"), Date.valueOf("1993-07-16"), Date.valueOf("1994-01-12"), Date.valueOf("1994-07-11"),
//				Date.valueOf("1995-01-07"), Date.valueOf("1995-07-06"), Date.valueOf("1996-01-02"), Date.valueOf("1996-06-30"),
//				Date.valueOf("1996-12-27"), Date.valueOf("1997-06-25"), Date.valueOf("1997-12-22"), Date.valueOf("1998-06-20"),
//				Date.valueOf("1998-12-17"), Date.valueOf("1999-06-15"), Date.valueOf("1999-12-12"), Date.valueOf("2000-06-09"),
//				Date.valueOf("2000-12-06"), Date.valueOf("2001-06-04"), Date.valueOf("2001-12-01"), Date.valueOf("2002-05-30"),
//				Date.valueOf("2002-11-26") };
//		
//		float[] maxCutLossArr = { -5000, -6000, -7000, -8000, -9000, -10000, -11000, -12000, -13000, -14000, -15000, -16000,
//				-17000, -18000, -19000, -20000, -21000, -22000, -23000, -24000, -25000, -26000, -27000, -28000, -29000, -30000 };
//
//		for (Date dt:dateArr){
//			SimulateSET50AndTFEX.START_DATE = dt;
//			
//			for (float maxTfexCutLoss:maxCutLossArr) {
//				SimulateSET50AndTFEX.CUT_LOSS_AMOUNT_TFEX = maxTfexCutLoss;
//				ths = new SimulateSET50AndTFEX();
//				ths.execute(false);
//				
//				SimulationData simDat = new SimulationData();
//				simDat.setStartDate(ths.simulationDataArray.get(0).getStartDate());
//				simDat.setEndDate(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getEndDate());
//				simDat.setBalance(ths.simulationDataArray.get(ths.simulationDataArray.size() - 1).getBalance());
//				totalSimulationResult.add(simDat);
//			}
//			
//			System.out.print(totalSimulationResult.get(0).getStartDate() + ";");
//			for(SimulationData simDat : totalSimulationResult){
//				System.out.format("%.2f;", simDat.getBalance());
//			}
//			System.out.println("");
//			totalSimulationResult.clear();
//
//			
//		}
		
	}
	
	public void execute(boolean printDetailedSimulation){
		
		//Indicator of the last operation
		boolean wasSold = true;
		boolean firstTime = true;
		float runningBalance = INITIAL_CAPITAL;
		
		try {
			
			weeklyData = loadMarketData(weeklyFileName);
			dailyData = loadMarketData(dailyFileName);
			
			Iterator<DateTime> itrWeekly = weeklyData.keySet().iterator();
			Iterator<DateTime> itrDaily = dailyData.keySet().iterator();
			MarketData thisWeek = null;
			MarketData today = null;
			
			//run the daily iterator to get the fist record ready
			if(itrDaily.hasNext()){
				today = dailyData.get(itrDaily.next());
			}
			
			//Start looping through the weekly data to find a start-point, either to short or buy SET50
			while(itrWeekly.hasNext()){
				thisWeek = weeklyData.get(itrWeekly.next());
				if(thisWeek.getHighIndex() < thisWeek.getCloseIndexSMAV()){
					wasSold = false;
					break;
				}else if(thisWeek.getLowIndex() > thisWeek.getCloseIndexSMAV()){
					wasSold = true;
					break;
				}
			}
			
			SimulationData simulationItem = new SimulationData();
			
			do{
				
				if(firstTime){
					
					while(today.getDate().compareTo(thisWeek.getDate()) < 0 && itrDaily.hasNext()){
						//Loop run to the start of the week
						today = dailyData.get(itrDaily.next());
					}
					
					while(today.getCloseIndex() != thisWeek.getCloseIndex() && itrDaily.hasNext()){
						//Loop run to the exact start date
						today = dailyData.get(itrDaily.next());
					}
					
					if(wasSold){
						simulationItem.setTypeOfTransaction(TransactionType.BUY_SET50);
					}else{
						simulationItem.setTypeOfTransaction(TransactionType.SHORT_TEFX);
						simulationItem.setNumberOfTFEXContract(getAllowedTFEXContract(INITIAL_CAPITAL));
					}
					
					simulationItem.setStartDate(today.getDate());
					simulationItem.setStartIndex(today.getCloseIndex());
					
					firstTime = false;
				} else {

					if (simulationItem.getTypeOfTransaction().equals(TransactionType.SHORT_TEFX)) {
						/*
						 * This is being short, so check if this week is the
						 * ending for short by checking if candle stick is below
						 * the line
						 */
						
						while(today.getDate().compareTo(thisWeek.getDate()) < 0 && itrDaily.hasNext()){
							//Loop until the date of the week
							today = dailyData.get(itrDaily.next());
							if(simulationItem.getStartIndex() - today.getCloseIndex() < simulationItem.getMaximumPointLossForPeriod()){
								simulationItem.setMaximumPointLossForPeriod(simulationItem.getStartIndex() - today.getCloseIndex());
							}
							if(((simulationItem.getStartIndex() - today.getCloseIndex()) * TFEX_COST_PER_POINT ) < CUT_LOSS_AMOUNT_TFEX
									&& simulationItem.getCutLossDate().equals(Date.valueOf("0001-01-01"))){
								//Mark a cut loss point, but still let the logic run
								simulationItem.setCutLossDate(today.getDate());
								simulationItem.setCutLossAmount(CUT_LOSS_AMOUNT_TFEX * getAllowedTFEXContract(runningBalance));
							}
						}
						
						while(today.getCloseIndex() != thisWeek.getCloseIndex() && itrDaily.hasNext()){
							//Loop run to the exact closing day of the week
							today = dailyData.get(itrDaily.next());
							if(simulationItem.getStartIndex() - today.getCloseIndex() < simulationItem.getMaximumPointLossForPeriod()){
								simulationItem.setMaximumPointLossForPeriod(simulationItem.getStartIndex() - today.getCloseIndex());
							}
							if(((simulationItem.getStartIndex() - today.getCloseIndex()) * TFEX_COST_PER_POINT ) < CUT_LOSS_AMOUNT_TFEX
									&& simulationItem.getCutLossDate().equals(Date.valueOf("0001-01-01"))){
								//Mark a cut loss point, but still let the logic run
								simulationItem.setCutLossDate(today.getDate());
								simulationItem.setCutLossAmount(CUT_LOSS_AMOUNT_TFEX * getAllowedTFEXContract(runningBalance));
							}
						}
						
						simulationItem.setEndDate(today.getDate());
						simulationItem.setEndIndex(today.getCloseIndex());
						if(simulationItem.getCutLossDate().equals(Date.valueOf("0001-01-01"))){
							simulationItem.setBalance(((simulationItem.getStartIndex() - today.getCloseIndex()) * TFEX_COST_PER_POINT * getAllowedTFEXContract(runningBalance)) + runningBalance);
						}else{
							simulationItem.setBalance(runningBalance + (CUT_LOSS_AMOUNT_TFEX * getAllowedTFEXContract(runningBalance)));
						}
//						simulationItem.setBalance(()
						
						if(thisWeek.getLowIndex() > thisWeek.getCloseIndexSMAV()){
							//Finish simulation item, add it to the list, and create a new one for BUY
							simulationDataArray.add(simulationItem);
							//Update runningBalance
							runningBalance = simulationItem.getBalance();
							
							//New SimulationItem for the Long side
							simulationItem = new SimulationData();
							simulationItem.setTypeOfTransaction(TransactionType.BUY_SET50);
							simulationItem.setStartDate(today.getDate());
							simulationItem.setStartIndex(today.getCloseIndex());
						}
						
					} else if (simulationItem.getTypeOfTransaction().equals(TransactionType.BUY_SET50)) {
						/*
						 * This is being long, so check if this week is the
						 * ending for long by checking if candle stick is above
						 * the line
						 */
						
						while(today.getDate().compareTo(thisWeek.getDate()) < 0 && itrDaily.hasNext()){
							//Loop until the date of the week
							today = dailyData.get(itrDaily.next());
							if(today.getCloseIndex() - simulationItem.getStartIndex() < simulationItem.getMaximumPointLossForPeriod()){
								simulationItem.setMaximumPointLossForPeriod(today.getCloseIndex() - simulationItem.getStartIndex());
							}
							if(((today.getCloseIndex() - simulationItem.getStartIndex()) /  simulationItem.getStartIndex()) < CUT_LOSS_PERCENTAGE
									&& simulationItem.getCutLossDate().equals(Date.valueOf("0001-01-01"))){
								//Mark a cut loss point, but still let the logic run
								simulationItem.setCutLossDate(today.getDate());
								simulationItem.setCutLossAmount(CUT_LOSS_PERCENTAGE * runningBalance);
							}
						}
						
						while(today.getCloseIndex() != thisWeek.getCloseIndex() && itrDaily.hasNext()){
							//Loop run to the exact closing day of the week
							today = dailyData.get(itrDaily.next());
							if(today.getCloseIndex() - simulationItem.getStartIndex() < simulationItem.getMaximumPointLossForPeriod()){
								simulationItem.setMaximumPointLossForPeriod(today.getCloseIndex() - simulationItem.getStartIndex());
							}
							if(((today.getCloseIndex() - simulationItem.getStartIndex()) /  simulationItem.getStartIndex()) < CUT_LOSS_PERCENTAGE
									&& simulationItem.getCutLossDate().equals(Date.valueOf("0001-01-01"))){
								//Mark a cut loss point, but still let the logic run
								simulationItem.setCutLossDate(today.getDate());
								simulationItem.setCutLossAmount(CUT_LOSS_PERCENTAGE * runningBalance);
							}
						}
						
						simulationItem.setEndDate(today.getDate());
						simulationItem.setEndIndex(today.getCloseIndex());
						if(simulationItem.getCutLossDate().equals(Date.valueOf("0001-01-01"))){
							simulationItem.setBalance((today.getCloseIndex() - simulationItem.getStartIndex())/simulationItem.getStartIndex()*runningBalance + runningBalance);
						}else{
							simulationItem.setBalance((CUT_LOSS_PERCENTAGE * runningBalance) + runningBalance);
						}
						
						if(thisWeek.getHighIndex() < thisWeek.getCloseIndexSMAV()){
							//Finish simulation item, add it to the list, and create a new one for SHORT
							simulationDataArray.add(simulationItem);
							//Update runningBalance
							runningBalance = simulationItem.getBalance();
							
							//New SimulationItem for the Long side
							simulationItem = new SimulationData();
							simulationItem.setTypeOfTransaction(TransactionType.SHORT_TEFX);
							simulationItem.setStartDate(today.getDate());
							simulationItem.setStartIndex(today.getCloseIndex());
							simulationItem.setNumberOfTFEXContract(getAllowedTFEXContract(runningBalance));
						}
						
					}

				}
				
				thisWeek = weeklyData.get(itrWeekly.next());
				
			}while(itrWeekly.hasNext());
			
			if(printDetailedSimulation){
				System.out.println("StartDate;StartIndex;Type;EndDate;EndIndex;NoOfTFEXContrace;MaxLoss;CutLossDate;CutLossAmt;Balance");
				for(SimulationData simulateItem:simulationDataArray){
					System.out.println(simulateItem.getStartDate() + ";" + simulateItem.getStartIndex() + ";"
							+ simulateItem.getTypeOfTransaction() + ";"
							+ simulateItem.getEndDate() + ";" + simulateItem.getEndIndex() + ";" + simulateItem.getNumberOfTFEXContract() + ";"
							+ simulateItem.getMaximumPointLossForPeriod() + ";" + simulateItem.getCutLossDate()
							+  ";" + numberFormat.format(simulateItem.getCutLossAmount()) + ";" + numberFormat.format(simulateItem.getBalance()));
				}
				
				System.out.println("================ Done ================");
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private TreeMap<DateTime, MarketData> loadMarketData(String fileToLoad) throws IOException{
		
		TreeMap<DateTime, MarketData> output = new TreeMap<DateTime, MarketData>();
		
//		FileInputStream fstream = new FileInputStream(fileToLoad);
		InputStream fstream = this.getClass().getResourceAsStream("/" + fileToLoad);
		
		DataInputStream in = new DataInputStream(fstream);
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		
		/*
		 * Initialize strLine with the first line, 
		 * supposed to be hearder, so read and throw away.
		 */
		String strLine = br.readLine();
		
		while ((strLine = br.readLine()) != null) {
			
			/*
			 * To clear out all excess space characters,
			 * so that we can "split" correctly
			 */
			while(strLine.indexOf("  ") > 0){
				strLine = strLine.replaceAll("  ", " ");
			}
			
			MarketData marketData = new MarketData();
			
			String[] lineItems = strLine.split(" ");
			DateTime date = new DateTime(Integer.parseInt(getYear(lineItems[0])),
					Integer.parseInt(getMonth(lineItems[0])),
					Integer.parseInt(getDay(lineItems[0])),
					0,0);
			
			DateTime date10Years = new DateTime(START_DATE).plusYears(SIMULATION_LENGTH_IN_YEAR).plusMonths(3);
			
			if(date.isAfter(START_DATE.getTime())
					&& date.isBefore(date10Years.getMillis())){
				
				marketData.setDate(new Date(date.getMillis()));
				marketData.setHighIndex(Float.parseFloat(lineItems[2].replaceAll(",","")));
				marketData.setLowIndex(Float.parseFloat(lineItems[3].replaceAll(",","")));
				marketData.setCloseIndex(Float.parseFloat(lineItems[4].replaceAll(",","")));
				marketData.setCloseIndexSMAV(Float.parseFloat(lineItems[5].replaceAll(",","")));
				marketData.setVolume(Float.parseFloat(lineItems[6].replaceAll(",","")));
				marketData.setVolumeSMAV(Float.parseFloat(lineItems[6].replaceAll(",","")));

				output.put(date, marketData);
				
			}
			
		}
		
		br.close();
		in.close();
		fstream.close();
		
		return output;
		
	}
	
	/**
	 * To get 4 digits year part from the raw date from the input file.
	 * 
	 * @param rawDate - dd/mm/yy
	 * @return 4 digits year yyyy
	 */
	private String getYear(String rawDate){
		String[] tmpStr = rawDate.split("/");
		if(tmpStr.length == 3
				&& tmpStr[2].charAt(0) == '9'){
			return "19" + tmpStr[2];
		}else{
			return "20" + tmpStr[2];
		}
	}
	
	/**
	 * To get 2 digits month part from the raw date from the input file.
	 * 
	 * @param rawDate - dd/mm/yy
	 * @return 2 digits month mm
	 */
	private String getMonth(String rawDate){
		String[] tmpStr = rawDate.split("/");
		if(tmpStr.length == 3){
			return tmpStr[1];
		}else{
			return "";
		}
	}

	/**
	 * To get 2 digits day part from the raw date from the input file.
	 * 
	 * @param rawDate - dd/mm/yy
	 * @return 2 digits day dd
	 */
	private String getDay(String rawDate){
		String[] tmpStr = rawDate.split("/");
		if(tmpStr.length == 3){
			return tmpStr[0];
		}else{
			return "";
		}
	}
	
	private int getAllowedTFEXContract(float balance){
		String[] splt = Float.toString(balance * CUT_LOSS_PERCENTAGE / CUT_LOSS_AMOUNT_TFEX).split("\\.");
		return Integer.parseInt(splt[0]);
	}
	
	private static float getApproxEquivalentInterestPerYear(float initialCapital, float endedBalance, long days){
		
		int capPeriod = 180;
		float initInterest = 0.001f;
		float newEndedBalance = initialCapital;
		
		while(Math.abs((endedBalance - newEndedBalance)/endedBalance) > 0.002){
			
			int i=1;
			newEndedBalance = initialCapital;
			do{
				newEndedBalance = newEndedBalance * (1 + (initInterest / 2));
				i = i+capPeriod;
			}while(i <= days);
			
			initInterest += 0.0001f;
		}
		
		return initInterest;
	}

}
